<?php

namespace Xy\Rbac\Middleware;

use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Xy\Rbac\Models\RbacPowerPageModel;
use Xy\Rbac\Models\RbacRoleModel;
use Xy\Rbac\Repositories\RbacPowerPageRepository;
use Xy\Rbac\Repositories\RbacUserRoleRepository;

class AccessAuthenticateMiddleware {
    static $responseCodeKeyName = 'code';
    static $responseMsgKeyName  = 'message';
    
    //用户权限缓存相关配置
    static $cacheDriver      = 'file';//缓存驱动
    static $cacheKeyPrefix   = 'rbac:';//缓存key前缀
    static $cacheSwitch      = false;//是否开启权限缓存
    static $cacheDurationMin = 1;//缓存分钟数
    
    const MIDDLE_WARE_NAME = 'xy.rbac.access';
    
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure                 $next
     * @param  string|null              $guard
     * @param  string|null              $loginUserNoLimitActionList
     *
     * @return mixed
     */
    public function handle($request, Closure $next) {
        $controller = null;
        
        if ($request->route()->controller) {
            $controller = $request->route()->getController();
            $actionName = $this->_getControllerActionName();//访问方法名获取 e.g, index 、 update
            
            //无需登录可访问控制器action
            if (
                property_exists($controller, 'noAuthActionList')
                &&
                is_array($controller->noAuthActionList) && !empty($controller->noAuthActionList)
                &&
                in_array($actionName, $controller->noAuthActionList)
            ) {
                return $next($request);
            }
        }
        
        $user = \Xy\Rbac\Providers\RbacServiceProvider::getAuthGuardUser();
        if (!$user) {
            //未登陆不可访问
            return new JsonResponse([
                self::$responseCodeKeyName => 401,
                self::$responseMsgKeyName  => 'Unauthorized.',
            ], 401);
        }
        
        //如果为通过控制器访问则判断action信息
        if ($controller) {
            //如果设置该控制器下的所有页面都无需权限验证 或者 为超级管理员可直接访问页面
            if (property_exists($controller, 'exceptAllAction') && $controller->exceptAllAction) {
                return $next($request);
            }
            
            //在控制器中定义不需要权限可访问页面可直接访问
            $exceptActionList = property_exists($controller, 'exceptActionList') ? $controller->exceptActionList : [];
            $actionName       = $this->_getControllerActionName();//访问方法名获取 e.g, index 、 update
            if (in_array($actionName, $exceptActionList)) {
                //只要登陆都可以访问的页面
                return $next($request);
            }
        }
        
        //检验需要权限的页面用户是否有权限可以访问
        return $this->checkAccessByUserIdAndRequestApi(\Xy\Rbac\Providers\RbacServiceProvider::getUserId()) ?
            $next($request)
            :
            response([
                self::$responseCodeKeyName => 403,
                self::$responseMsgKeyName  => 'Unauthorized. Page access deny. 您无权访问接口:' . $_SERVER['REQUEST_URI'],
            ], 403);
    }
    
    /**
     * 检测用户权限
     *
     * @param $userId
     *
     * @return bool
     */
    protected function checkAccessByUserIdAndRequestApi($userId) {
    
        if (self::$cacheSwitch) {
            $userPowerApiList = $this->_getCacheStore()->get($this->_getCacheKeyByUserId($userId));
            if (!is_null($userPowerApiList)) {
                //超级管理员缓存为true
                if ($userPowerApiList === true) {
                    return true;
                }
            
                return is_array($userPowerApiList) && $this->_checkUserAccessToApi($userPowerApiList);
            }
        }
    
        $userRoleCol = RbacUserRoleRepository::getInstance()->getRoleByUserId($userId, ['role']);
        
        $userRoleIdList = $userRoleCol->pluck('role_id')->toArray();
        
        //如果为超级用户直接可以访问
        if (in_array(RbacRoleModel::SUPER_USER_ROLE_ID, $userRoleIdList)) {
            self::$cacheSwitch && $this->_addCacheData($userId, true);
            return true;
        }
        
        $powerIdList = [];
        foreach ($userRoleCol as $userRoleIndex => $userRoleEl) {
            if ($userRoleEl->role) {
                $rolePowerList = $userRoleEl->role->power;
                $powerIdList   = array_merge($powerIdList, $rolePowerList);
            }
        }
        
        $powerIdList       = array_unique($powerIdList);
        $userPowerApiList = RbacPowerPageRepository::getInstance()
            ->getPowerPageByIdList($powerIdList, function ($query) {
                $query->orderBy('order')
                    ->select(['api_uri', 'api_method', 'api_match_type'])
                    ->where('level', RbacPowerPageModel::LEVEL_API);
            })
            ->makeHidden(['meta', 'name', 'label'])
            ->toArray();
    
        self::$cacheSwitch && $this->_addCacheData($userId, $userPowerApiList);
        
        return $this->_checkUserAccessToApi($userPowerApiList);
    }
    
    /**
     * 获取访问控制器的方法名称
     * @return bool|string
     */
    protected function _getControllerActionName() {
        $actionInfo  = request()->route()->getAction();
        $actionRoute = $actionInfo['uses'];//e.g App\Http\Controllers\Admin\IndexController@index
        
        return substr(strrchr($actionRoute, "@"), 1);//访问方法名获取 e.g, index 、 update
    }
    
    /**
     * @return \Illuminate\Contracts\Cache\Repository
     */
    protected function _getCacheStore() {
        return Cache::store(self::$cacheDriver);
    }
    
    /**
     * @return string
     */
    protected function _getCacheKeyByUserId($userId) {
        return self::$cacheKeyPrefix . $userId;
    }
    
    /**
     * @param $userId
     * @param $data
     */
    protected function _addCacheData($userId, $data) {
        $this->_getCacheStore()->put($this->_getCacheKeyByUserId($userId), $data, self::$cacheDurationMin);
    }
    
    /**
     * @param $userPowerApiList
     *
     * @return bool
     */
    protected function _checkUserAccessToApi($userPowerApiList){
        $fullUri       = $_SERVER['REQUEST_URI'];//完整的uri,e.g /api/settings/users/1/assign-branch
        $requestMethod = strtoupper(request()->getMethod());//e.g,POST 、PUT
        $accessUri     = request()->route()->uri();//laravel 路由uri e.g, api/settings/users/{id}/assign-branch
        $accessUri     = substr($accessUri, 0, 1) == '/' ? substr($accessUri, 1) : $accessUri;
        
        $userAccessCheck = false;
    
        //获取用户api权限
        foreach ($userPowerApiList as $powerApiInfo) {
            $powerApiInfo['api_method'] = strtoupper($powerApiInfo['api_method']);
        
            if ($powerApiInfo['api_method'] != $requestMethod) {
                continue;
            }
        
            if ($powerApiInfo['api_match_type'] == 0) {//laravel路由匹配
                $powerApiInfo['api_uri'] = substr($powerApiInfo['api_uri'], 0, 1) == '/' ? substr($powerApiInfo['api_uri'], 1) : $powerApiInfo['api_uri'];
                if ($powerApiInfo['api_uri'] == $accessUri) {
                    $userAccessCheck = true;
                    break;
                }
            } else if ($powerApiInfo['api_match_type'] == 1) {//匹配完整路由
            
                if ($powerApiInfo['api_uri'] == $fullUri) {
                    $userAccessCheck = true;
                    break;
                }
            
            } else if ($powerApiInfo['api_match_type'] == 2) {//正则匹配
            
                if (preg_match($powerApiInfo['api_uri'], $fullUri)) {
                    $userAccessCheck = true;
                    break;
                }
            
            }
        }
    
        return $userAccessCheck;
    }
}
